package com.zeyu.framework.tools.schedule.service;

import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import com.zeyu.framework.tools.schedule.entity.JobEntity;
import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.collections.MapUtils;
import org.apache.commons.collections.set.ListOrderedSet;
import org.apache.commons.lang3.StringUtils;
import org.quartz.CronScheduleBuilder;
import org.quartz.CronTrigger;
import org.quartz.JobBuilder;
import org.quartz.JobDataMap;
import org.quartz.JobDetail;
import org.quartz.JobKey;
import org.quartz.ScheduleBuilder;
import org.quartz.SchedulerException;
import org.quartz.SimpleScheduleBuilder;
import org.quartz.SimpleTrigger;
import org.quartz.Trigger;
import org.quartz.TriggerBuilder;
import org.quartz.TriggerKey;
import org.quartz.impl.matchers.GroupMatcher;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.scheduling.quartz.SchedulerFactoryBean;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.text.MessageFormat;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.TimeUnit;

/**
 * quartz service
 * Created by zeyuphoenix on 16/6/20.
 */
@Service
public class JobService {

    // ================================================================
    // Constants
    // ================================================================

    /**
     * logger
     */
    private static final Logger logger = LoggerFactory.getLogger(JobService.class);

    // ================================================================
    // Fields
    // ================================================================

    @Autowired
    private SchedulerFactoryBean schedulerFactoryBean;

    // ================================================================
    // Constructors
    // ================================================================

    // ================================================================
    // Methods from/for super Interfaces or SuperClass
    // ================================================================

    // ================================================================
    // Public or Protected Methods
    // ================================================================

    /**
     * 取得组下的Job列表,无分页
     *
     * @param group 组名
     * @return Job列表
     */
    public List<JobEntity> find(String group) {
        List<JobEntity> entityList = Lists.newArrayList();
        try {
            GroupMatcher<JobKey> groupEquals;
            if (StringUtils.isEmpty(group)
                    || StringUtils.endsWithIgnoreCase(group,
                    JobEntity.NODE_ROOT)) {
                // 处理根节点
                group = "";
                groupEquals = GroupMatcher.groupContains(group);
            } else {
                groupEquals = GroupMatcher.groupEquals(group);
            }

            // 取得JOB Key.
            Set<JobKey> jobKeys = schedulerFactoryBean.getScheduler().getJobKeys(groupEquals);
            if (!CollectionUtils.isEmpty(jobKeys)) {
                // 转换成有顺序的，利于分页和展示
                List<?> keys = ListOrderedSet.decorate(jobKeys).asList();

                for (Object key : keys) {
                    JobEntity job = getJobEntity(((JobKey) key).getGroup(),
                            ((JobKey) key).getName());
                    job.setId(job.getName() + job.getGroup());
                    entityList.add(job);
                }
            }
        } catch (SchedulerException e) {
            logger.error(MessageFormat.format("查询 {0} 组的任务列表异常: {1}", group,
                    e.getMessage()));
            throw new RuntimeException(MessageFormat.format(
                    "查询 {0} 组的任务列表异常: {1}", group, e.getMessage()));
        }
        return entityList;
    }


    /**
     * 取得Job的树
     */
    public List<Map<String, Object>> treeData() {

        List<Map<String, Object>> mapList = Lists.newArrayList();

        try {
            List<String> triggerGroups = schedulerFactoryBean.getScheduler().getTriggerGroupNames();
            //root下的所有节点
            Map<String, Object> root = Maps.newHashMap();
            root.put(JobEntity.ATTR_ID, JobEntity.NODE_ROOT);
            root.put(JobEntity.ATTR_PID, "0");
            root.put(JobEntity.ATTR_NAME, "<i class='fa fa-home txt-color-blueLight'></i> " + JobEntity.NODE_ROOT_NAME);
            root.put(JobEntity.ATTR_TYPE, JobEntity.TYPE_GROUP);
            mapList.add(root);

            for (String triggerGroup : triggerGroups) {
                //group下的所有节点
                Map<String, Object> child = Maps.newHashMap();
                child.put(JobEntity.ATTR_ID, triggerGroup);
                child.put(JobEntity.ATTR_PID, JobEntity.NODE_ROOT);
                child.put(JobEntity.ATTR_NAME, "<i class='fa fa-tasks'></i> " + triggerGroup);
                child.put(JobEntity.ATTR_TYPE, JobEntity.TYPE_GROUP);
                mapList.add(child);

                GroupMatcher<TriggerKey> match = GroupMatcher
                        .groupEquals(triggerGroup);
                Set<TriggerKey> set = schedulerFactoryBean.getScheduler().getTriggerKeys(match);
                for (TriggerKey triggerKey : set) {
                    Map<String, Object> leaf = Maps.newHashMap();
                    Trigger t = schedulerFactoryBean.getScheduler().getTrigger(triggerKey);
                    JobDetail job = schedulerFactoryBean.getScheduler().getJobDetail(t.getJobKey());
                    leaf.put(JobEntity.ATTR_ID, job.getKey().getName());
                    leaf.put(JobEntity.ATTR_PID, job.getKey().getGroup());
                    leaf.put(JobEntity.ATTR_NAME, "<i class='fa fa-calendar-check-o'></i> " + job.getKey().getName());
                    leaf.put(JobEntity.ATTR_TYPE, JobEntity.TYPE_JOB);
                    mapList.add(leaf);
                }
            }

        } catch (SchedulerException e) {
            logger.error(MessageFormat.format("构造任务树异常: {0}", e.getMessage()));
            throw new RuntimeException(MessageFormat.format("构造任务树异常: {0}",
                    e.getMessage()));
        }

        return mapList;
    }

    /**
     * 取得一个Job的详细信息
     * 必须参数： 任务ID 格式：”任务组.任务名“
     */
    public JobEntity getJobEntity(String group, String name) {
        JobEntity entity;
        try {
            JobKey key = JobKey.jobKey(name, group);
            JobDetail detail = schedulerFactoryBean.getScheduler().getJobDetail(key);
            if (detail == null) {
                logger.info("no job detail find, return null entity.");
                return null;
            }
            entity = new JobEntity();
            JobEntity.JobRuntimeEntity runtimeEntity = new JobEntity.JobRuntimeEntity();

            entity.setName(detail.getKey().getName());
            entity.setGroup(detail.getKey().getGroup());
            entity.setDescription(detail.getDescription());
            entity.setJobClass(detail.getJobClass());

            // create used, read for load time.
            // entity.setDatas(detail.getJobDataMap().getWrappedMap());
            Map<String, Object> datas = detail.getJobDataMap().getWrappedMap();
            if (MapUtils.isNotEmpty(datas)) {
                Object runInfo = datas.get(JobEntity.ENTITY_KEY);
                entity.setHistory(detail.getJobDataMap().getBoolean("history"));
                if (runInfo != null) {
                    JobEntity runEntity = (JobEntity) runInfo;
                    if (runEntity.getJobRuntimeEntity() != null) {
                        runtimeEntity.setFireCount(runEntity.getJobRuntimeEntity()
                                .getFireCount());
                        runtimeEntity.setFireTime(runEntity.getJobRuntimeEntity()
                                .getFireTime());
                        runtimeEntity.setScheduledFireTime(runEntity
                                .getJobRuntimeEntity().getScheduledFireTime());
                    }
                }
            }

            entity.setPersistJobDataAfterExecution(detail.isDurable());
            entity.setPersistJobDataAfterExecution(detail
                    .isPersistJobDataAfterExecution());
            entity.setType(1);

            Trigger trigger = schedulerFactoryBean.getScheduler().getTrigger(TriggerKey.triggerKey(name,
                    group));
            if (trigger == null) {
                return entity;
            }
            entity.setStartDate(trigger.getStartTime());
            entity.setEndDate(trigger.getEndTime());

            runtimeEntity.setNextFireTime(trigger.getNextFireTime());
            runtimeEntity.setPreviousFireTime(trigger.getPreviousFireTime());
            entity.setJobRuntimeEntity(runtimeEntity);

            if (trigger instanceof SimpleTrigger) {
                SimpleTrigger simpleTrigger = (SimpleTrigger) trigger;
                TimeUnit unit = TimeUnit.SECONDS;
                entity.setInterval((int) unit.convert(
                        simpleTrigger.getRepeatInterval(),
                        TimeUnit.MILLISECONDS));
                entity.setUnit(unit);

            } else if (trigger instanceof CronTrigger) {
                CronTrigger cronTrigger = (CronTrigger) trigger;
                entity.setInterval(0);
                entity.setUnit(null);
                entity.setCron(cronTrigger.getCronExpression());
            }
            try {
                Trigger.TriggerState state = schedulerFactoryBean.getScheduler().getTriggerState(TriggerKey
                        .triggerKey(name, group));
                entity.getJobRuntimeEntity().setStatus(state.ordinal());
            } catch (Exception e) {
                entity.getJobRuntimeEntity().setStatus(Trigger.TriggerState.NONE.ordinal());
            }

        } catch (Exception e) {
            throw new RuntimeException("查询任务失败");
        }
        return entity;
    }

    /**
     * 取得一个Job的详细信息
     * 必须参数： 任务ID 格式：”任务组.任务名“
     */
    public JobEntity getJobEntity(JobEntity entity) {
        // 调用其他方法
        return getJobEntity(entity.getGroup(), entity.getName());
    }

    /**
     * 添加一个新的Job
     * 必须包含Job Entity的必选项
     */
    @Transactional()
    public void addJob(JobEntity entity) {
        ScheduleBuilder<?> scheduleBuilder = createScheduleBuilder(entity);
        try {
            // 验证任务是否存在
            boolean isExit = schedulerFactoryBean.getScheduler().checkExists(JobKey.jobKey(
                    entity.getName(), entity.getGroup()));
            if (isExit) {
                logger.info(MessageFormat.format("添加任务，名：{0}，组：{1}已经存在，重置任务",
                        entity.getName(), entity.getGroup()));
                //如果有datas,则从JobDetail中获取并set到entity中
                entity.setDatas(schedulerFactoryBean.getScheduler().getJobDetail(JobKey.jobKey(
                        entity.getName(), entity.getGroup())).getJobDataMap());
                this.deleteJob(entity);
            }

            // 是否保存历史
            entity.getDatas().put("history", entity.isHistory() + "");

            // 任务对象
            // 任务名，任务组，任务执行类
            JobDetail detail = JobBuilder
                    .newJob(entity.getJobClass())
                    .withDescription(entity.getDescription())
                    .withIdentity(
                            JobKey.jobKey(entity.getName(), entity.getGroup()))
                    .setJobData(new JobDataMap(entity.getDatas())).usingJobData(new JobDataMap(entity.getDatas()))
                    .storeDurably().build();

            // 触发器
            Trigger trigger = TriggerBuilder
                    .newTrigger()
                    .withIdentity(
                            TriggerKey.triggerKey(entity.getName(),
                                    entity.getGroup())).forJob(detail)
                    .startAt(entity.getStartDate()).endAt(entity.getEndDate())
                    .withSchedule(scheduleBuilder).build();

            schedulerFactoryBean.getScheduler().scheduleJob(detail, trigger);
            logger.debug(MessageFormat.format("添加任务，名：{0}，组：{1}。",
                    entity.getName(), entity.getGroup()));
        } catch (SchedulerException e) {
            logger.error(MessageFormat.format("添加任务，名:{0}，组：{1}，失败：{2}。",
                    entity.getName(), entity.getGroup(), e.getMessage()), e);
            throw new RuntimeException(MessageFormat.format(
                    "添加任务，名:{0}，组：{1}，失败：{2}。", entity.getName(),
                    entity.getGroup(), e.getMessage()));
        }
    }

    /**
     * 删除一个Job
     * 必须参数： 任务ID 格式：”任务组.任务名“
     */
    @Transactional()
    public void deleteJob(JobEntity entity) {
        try {
            // 停止触发器
            schedulerFactoryBean.getScheduler().pauseTrigger(TriggerKey.triggerKey(entity.getName(),
                    entity.getGroup()));
            // 移除触发器
            schedulerFactoryBean.getScheduler().unscheduleJob(TriggerKey.triggerKey(entity.getName(),
                    entity.getGroup()));
            // 删除任务
            schedulerFactoryBean.getScheduler().deleteJob(JobKey.jobKey(entity.getName(),
                    entity.getGroup()));
            logger.debug(MessageFormat.format("删除任务，名：{0}，组：{1}。",
                    entity.getName(), entity.getGroup()));
        } catch (SchedulerException e) {
            logger.error(MessageFormat.format("删除任务，名：{0}，组：{1}，失败:{2}。",
                    entity.getName(), entity.getGroup(), e.getMessage()));
            throw new RuntimeException(MessageFormat.format(
                    "删除任务，名：{0}，组：{1}，失败:{2}。", entity.getName(),
                    entity.getGroup(), e.getMessage()));
        }
    }

    /**
     * 暂停一个Job
     * 注意：暂停任务，不代表中间有段时间会中断，在任务恢复时，暂停的那段时间会依次执行
     * 必须参数： 任务ID 格式：”任务组.任务名“
     */
    @Transactional()
    public void pauseJob(JobEntity entity) {
        try {
            schedulerFactoryBean.getScheduler().pauseJob(JobKey.jobKey(entity.getName(),
                    entity.getGroup()));
            logger.debug(MessageFormat.format("暂停任务，名：{0}，组：{1}。",
                    entity.getName(), entity.getGroup()));
        } catch (SchedulerException e) {
            logger.error(MessageFormat.format("暂停任务，名：{0}，组：{1}，失败:{}2。",
                    entity.getName(), entity.getGroup(), e.getMessage()));
            throw new RuntimeException(MessageFormat.format(
                    "暂停任务，名：{0}，组：{1}，失败:{2}。", entity.getName(),
                    entity.getGroup(), e.getMessage()));
        }

    }

    /**
     * 恢复一个Job
     * 注意：在任务恢复时，暂停任务的那段时间会依次执行
     * 必须参数： 任务ID 格式：”任务组.任务名“
     */
    @Transactional()
    public void resumeJob(JobEntity entity) {
        try {
            schedulerFactoryBean.getScheduler().resumeJob(JobKey.jobKey(entity.getName(),
                    entity.getGroup()));
            logger.debug(MessageFormat.format("恢复任务，名：{0}，组：{1}。",
                    entity.getName(), entity.getGroup()));
        } catch (SchedulerException e) {
            logger.error(MessageFormat.format("恢复任务，名：{0}，组：{1}，失败:{2}。",
                    entity.getName(), entity.getGroup(), e.getMessage()));
            throw new RuntimeException(MessageFormat.format(
                    "恢复任务，名：{0}，组：{1}，失败:{2}。", entity.getName(),
                    entity.getGroup(), e.getMessage()));
        }
    }

    /**
     * 立即执行一个Job,一般测试用
     * 必须参数： 任务ID 格式：”任务组.任务名“
     */
    @Transactional()
    public void ImmediateExecJob(JobEntity entity) {
        try {

            // 立即执行
            schedulerFactoryBean.getScheduler().triggerJob(JobKey.jobKey(entity.getName(),
                    entity.getGroup()));

            logger.debug(MessageFormat.format("立即执行任务，名：{0}，组：{1}。",
                    entity.getName(), entity.getGroup()));
        } catch (SchedulerException e) {
            logger.error(MessageFormat.format("立即执行任务，名：{0}，组：{1}，失败:{2}。",
                    entity.getName(), entity.getGroup(), e.getMessage()));
            throw new RuntimeException(MessageFormat.format(
                    "立即执行任务，名：{0}，组：{1}，失败:{2}。", entity.getName(),
                    entity.getGroup(), e.getMessage()));
        }
    }

    // ================================================================
    // Getter & Setter
    // ================================================================

    // ================================================================
    // Private Methods
    // ================================================================

    /**
     * 根据任务实体创建任务
     *
     * @param entity 任务实体
     */
    private ScheduleBuilder<?> createScheduleBuilder(JobEntity entity) {
        // 调度
        ScheduleBuilder<?> scheduleBuilder;
        if (!StringUtils.isEmpty(entity.getCron())) {
            try {
                scheduleBuilder = CronScheduleBuilder.cronSchedule(entity
                        .getCron());
            } catch (Exception e) {
                logger.error("表达式解析异常:{}", e.getMessage());
                throw new RuntimeException("表达式解析异常:" + e.getMessage());
            }
        } else {
            if (entity.getInterval() <= 0)
                throw new RuntimeException("interval can not equal 0.");
            if (entity.getUnit() == null || entity.getUnit() == TimeUnit.SECONDS) {
                entity.setUnit(TimeUnit.SECONDS);
                Object[] results = convert(entity.getInterval());
                entity.setUnit((TimeUnit) results[0]);
                entity.setInterval((Integer) results[1]);
            }
            switch (entity.getUnit()) {
                case DAYS:
                    scheduleBuilder = SimpleScheduleBuilder
                            .repeatHourlyForever(entity.getInterval() * 24);
                    break;
                case HOURS:
                    scheduleBuilder = SimpleScheduleBuilder
                            .repeatHourlyForever(entity.getInterval());
                    break;
                case MINUTES:
                    scheduleBuilder = SimpleScheduleBuilder
                            .repeatMinutelyForever(entity.getInterval());
                    break;
                case SECONDS:
                    scheduleBuilder = SimpleScheduleBuilder
                            .repeatSecondlyForever(entity.getInterval());
                    break;
                default:
                    logger.error("不支持的时间间隔:" + entity.getUnit().name());
                    throw new RuntimeException("不支持的时间间隔:"
                            + entity.getUnit().name());
            }
        }
        return scheduleBuilder;
    }

    /**
     * 根据秒数换算单位 当无单位指定时使用
     *
     * @param second 任务执行秒数间隔
     */
    private Object[] convert(int second) {

        Object[] results = new Object[2];

        int daylong = 24 * 60 * 60;
        int hourlong = 60 * 60;
        int minutelong = 60;

        if (second > daylong) {
            if (second % daylong == 0) {
                results[0] = TimeUnit.DAYS;
                results[1] = second / daylong;
                return results;
            }
        }
        if (second > hourlong) {
            if (second % hourlong == 0) {
                results[0] = TimeUnit.HOURS;
                results[1] = second / hourlong;
                return results;
            }
        }
        if (second > minutelong) {
            if (second % minutelong == 0) {
                results[0] = TimeUnit.MINUTES;
                results[1] = second / minutelong;
                return results;
            }
        }
        results[0] = TimeUnit.SECONDS;
        results[1] = second;
        return results;
    }

    // ================================================================
    // Inner or Anonymous Class
    // ================================================================

    // ================================================================
    // Test Methods
    // ================================================================

}
